using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Threading;
namespace WOWEquipOptimizer
{
    [Flags]
    public enum CharacterClass { None = 0, Shaman = 1, Priest = 2, Mage = 4, Warrior = 8, Rogue = 16, Hunter = 32, Paladin = 64, Druid = 128, Warlock = 256 };
     /// <summary>
     /// Main abstract character class, includes constants, base functions for filtering items, handling items and evaluating equipment
     /// </summary>
    public abstract class Character : ICloneable
    {
        public const float WeaponSkillRating = 3.9f;
        public const float HitRating = 15.8f;
        public const float HitSpellRating = 12.6f;
        public const float CritRating = 22.1f;
        public const float HasteRating = 15.76f; //10.5f;
        public const float HasteSpellRating = 15.76f; //21f;
        public const float CritSpellRating = 22.1f;
        public const float DefenseSkillRating = 2.4f;
        public const float DodgeRating = 18.9f;
        public const float ParryRating = 22.4f;
        public const float BlockRating = 7.9f;
        public static float Armor = 0.85f;
        protected EquipCom m_Equip;
        private List<AttributeName> m_GoodAttributes = new List<AttributeName>();
        private List<AttributeName> m_BadAttributes = new List<AttributeName>();

        public List<AttributeName> BadAttributes
        {
            get { return m_BadAttributes; }
            set { m_BadAttributes = value; }
        }
        public List<AttributeName> GoodAttributes
        {
            get { return m_GoodAttributes; }
            set { m_GoodAttributes = value; }
        }
        private float m_CondFactor;

        public float CondFactor
        {
            get { return m_CondFactor; }
            set { m_CondFactor = value; }
        }
        protected Dictionary<AttributeName, float> m_Conditions = new Dictionary<AttributeName, float>();

        public Dictionary<AttributeName, float> Conditions
        {
            get { return m_Conditions; }
            set { m_Conditions = value; }
        }
        public float getData(AttributeName key)
        {
            return m_Equip[key];
        }
        protected List<Attribute> m_Base = new List<Attribute>();

        public List<Attribute> Base
        {
            get { return m_Base; }
            set { m_Base = value; }
        }
        private List<Buff> m_Buffs = new List<Buff>();

        public List<Buff> Buffs
        {
            get { return m_Buffs; }
            set { m_Buffs = value; }
        }
        private List<Enchant> m_Enchants = new List<Enchant>();
        /// <summary>
        /// Enchant attribute values
        /// </summary>
        public List<Enchant> Enchants
        {
          get { return m_Enchants; }
          set { m_Enchants = value; }
        }
        /// <summary>
        /// small hack to allow permanent buffs of Attribute.Special type
        /// </summary>
        protected int m_PermSpecialBuffs = 0;
        protected int m_Slotcount;
        protected Item[] m_Items;
        /// <summary>
        /// our equipped m_Items
        /// </summary>
        public Item[] Slots
        {
            get { return m_Items; }
            set { m_Items = value; }
        }
        public List<String> SpecialValues
        {
            get
            {
                return m_Equip.SpecialValues;
            }
            set
            {
                m_Equip.SpecialValues = value;
            }
        }
        private bool m_AllowTwoHand = true;

        public bool AllowTwoHand
        {
            get { return m_AllowTwoHand; }
            set { m_AllowTwoHand = value; }
        }
        private bool m_AllowShield = false;

        public bool AllowShield
        {
            get { return m_AllowShield; }
            set { m_AllowShield = value; }
        }
        private bool m_AllowDualWield = false;


        public bool AllowDualWield
        {
            get { return m_AllowDualWield; }
            set { m_AllowDualWield = value; }
        }
        /// <summary>
        /// Below is Black/Whitelist handling
        /// </summary>
        protected static List<string> m_SetBlacklist = new List<string>();

        public List<string> SetBlacklist
        {
            get { return m_SetBlacklist; }
            set { m_SetBlacklist = value; }
        }
        protected static List<string> m_ItemBlacklist = new List<string>();

        public List<string> ItemBlacklist
        {
            get { return m_ItemBlacklist; }
            set { m_ItemBlacklist = value; }
        }
        protected static List<string> m_ItemWhitelist = new List<string>();

        public List<string> ItemWhitelist
        {
            get { return m_ItemWhitelist; }
            set { m_ItemWhitelist = value; }
        }
        /// <summary>
        /// Weapon skill support
        /// </summary>
        protected List<WeaponSkill> m_WeaponSkills = new List<WeaponSkill>();
        protected List<WeaponSkill> m_BaseWeaponSkills = new List<WeaponSkill>();
        /// <summary>
        /// Constructor
        /// </summary>
        public Character()
        {
            Type ptype = this.GetType();
            try
            {
                m_CharClass = (CharacterClass)Enum.Parse(typeof(CharacterClass), ptype.FullName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries)[1]);
            }
            catch (Exception)
            {
            }
            m_Slotcount = Character.SlotCount;
            m_Equip = new EquipCom(this);
            //this.SpecialValues.Clear();
            m_Items = new Item[m_Slotcount];
            // Let's add array few generic buffs here
            Buff ai = new Buff("Arcane Intellect", new List<Attribute>());
            ai.Attributes.Add(new Attribute(AttributeName.Intellect, 40));
            Buff pwf = new Buff("Power Word: Fortitude", new List<Attribute>());
            pwf.Attributes.Add(new Attribute(AttributeName.Stamina, 79 * 1.3f));
            Buff ds = new Buff("Divine Spirit", new List<Attribute>());
            ds.Attributes.Add(new Attribute(AttributeName.Spirit, 50));
            Buff gotw = new Buff("Gift of the Wild", new List<Attribute>());
            gotw.Attributes.Add(new Attribute(AttributeName.Armor, 340 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.Agility, 14 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.Intellect, 14 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.Stamina, 14 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.Strength, 14 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.Spirit, 14 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.ShadowResist, 25 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.FireResist, 25 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.FrostResist, 25 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.NatureResist, 25 * 1.35f));
            gotw.Attributes.Add(new Attribute(AttributeName.ArcaneResist, 25 * 1.35f));
            Buffs.Add(ai);
            Buffs.Add(pwf);
            Buffs.Add(ds);
            Buffs.Add(gotw);
            Buff shadowpriest = new Buff("Shadowpriest MP5", new List<Attribute>());
            shadowpriest.Attributes.Add(new Attribute(AttributeName.MP5, 250f));
            shadowpriest.Enabled = false;
            Buffs.Add(shadowpriest);

            // useful attributes now
            float[] values = new float[Enum.GetNames(typeof(AttributeName)).Length];
            string[] usefulattrs = getAttributes();
            for (int i = 0; i < values.Length; i++)
            {
                AttributeName attrn = (AttributeName)i;
                handleAttribute(new Attribute(attrn, 100));
                for (int k = getAttributesStart(); k < usefulattrs.Length; k++)
                {
                    if (m_Equip[(AttributeName)Enum.Parse(typeof(AttributeName), usefulattrs[k], true)] != 0)
                    {
                        if (!m_GoodAttributes.Contains(attrn))
                        {
                            m_GoodAttributes.Add(attrn);
                        }
                    }                    
                }
                m_Equip.ResetBase();
            }
            m_GoodAttributes.Add(AttributeName.Special);
            m_GoodAttributes.Remove(AttributeName.Stamina);
            m_GoodAttributes.Remove(AttributeName.Health);
        }
        /// <summary>
        /// Load/Save black/white list
        /// </summary>
        public static void LoadLists()
        {
            try
            {
                FileStream fstream = new FileStream("blacklist.xml", FileMode.Open);
                XmlSerializer serialize = new XmlSerializer(typeof(List<string>));
                m_ItemBlacklist = (List<string>)serialize.Deserialize(fstream);
                fstream.Close();
                fstream = new FileStream("whitelist.xml", FileMode.Open);
                serialize = new XmlSerializer(typeof(List<string>));
                m_ItemWhitelist = (List<string>)serialize.Deserialize(fstream);
                fstream.Close();
            }
            catch (Exception)
            {
            }
        }
        public static void SaveLists()
        {
            try
            {
                FileStream fstream = new FileStream("blacklist.xml", FileMode.Create);
                XmlSerializer serialize = new XmlSerializer(typeof(List<string>));
                serialize.Serialize(fstream, m_ItemBlacklist);
                fstream.Close();
                fstream = new FileStream("whitelist.xml", FileMode.Create);
                serialize = new XmlSerializer(typeof(List<string>));
                serialize.Serialize(fstream, m_ItemWhitelist);
                fstream.Close();
            }
            catch (Exception)
            {
            }
        }
        // whitelist handling + class specific items
        public List<Item> handleWhiteList(List<Item> items)
        {
            foreach (string name in ItemWhitelist)
            {
                Item.CmpString = name;
                List<Item> itemlist = Database.Items.FindAll(Item.FindString);
                foreach (Item add in itemlist)
                {
                    if (!items.Contains(add))
                    {
                        items.Add(add);
                    }
                }
            }
            foreach (Item item in Database.Items)
            {
                if (item.AllowableClasses == this.m_CharClass)
                {
                    if (!items.Contains(item))
                    {
                        items.Add(item);
                    }
                }
            }
            return items;
        }
        /// <summary>
        /// abstract functions to be implemented by actual characters
        /// </summary>
        public abstract string targetValue();
        public virtual void handleAttribute(Attribute attr)
        {
            switch (attr.Type)
            {
                case AttributeName.Agility:
                case AttributeName.Spirit:
                case AttributeName.Strength:
                case AttributeName.Intellect:
                    m_Equip[attr.Type] += attr.Value * (HasBlessingOfKings ? 1.1f : 1.0f);
                    break;
                case AttributeName.Stamina:
                    m_Equip[attr.Type] += attr.Value * (HasBlessingOfKings ? 1.1f : 1.0f);
                    m_Equip[AttributeName.Health] += attr.Value * 10 * (HasBlessingOfKings ? 1.1f : 1.0f);
                    break;
                case AttributeName.ResistAll:
                    m_Equip[AttributeName.FireResist] += attr.Value;
                    m_Equip[AttributeName.ShadowResist] += attr.Value;
                    m_Equip[AttributeName.NatureResist] += attr.Value;
                    m_Equip[AttributeName.FrostResist] += attr.Value;
                    m_Equip[AttributeName.ArcaneResist] += attr.Value;
                    break;
                default:
                    m_Equip[attr.Type] += attr.Value;
                    break;
            }
        }
        public abstract bool evaluateBuild(int[] build);
        public abstract float evaluateTF();
        public abstract string[] getAttributes();
        private CharacterClass m_CharClass;

        public CharacterClass CharClass
        {
            get { return m_CharClass; }
            set { m_CharClass = value; }
        }
        /// <summary>
        /// hack for bok, currently no Attribute.Special
        /// </summary>
        public static bool HasBlessingOfKings = true;
        public static bool UseSpecials = true;
        public bool evaluateEquip()
        {
            return evaluateEquip(Slots);
        }
        /// <summary>
        /// we filter array few items in any case, for every character
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public virtual List<Item> filterEquip(List<Item> input)
        {
            List<Item> retval = new List<Item>();
            foreach (Item item in input)
            {
                if (item.Attributes.Count == 0) continue;
                if (item.Quality == Quality.Standard) continue;
                bool bad = true;
                for (int i = 0; i < item.Attributes.Count; i++)
                {
                    if (m_GoodAttributes.Contains(item.Attributes[i].Type))
                    {
                        // items with special attribute are usually just useful in combination with other attributes, except for totems,relicts etc
                        if (item.Attributes[i].Type == AttributeName.Special) continue;
                        bad = false;
                        break;
                    }
                }
                if(item.GetType() == typeof(Weapon))
                {
                    Weapon weapon = (Weapon)item;
                    if (weapon.WeaponSlot == WeaponSlot.Ranged && bad)
                    {
                        bad = false;
                    }
                }
                if (item.GetType() == typeof(Gem))
                {
                    Gem gem = (Gem)item;
                    if (gem.Level < 70) continue;
                    if (gem.Type == GemType.Meta) continue;
                    if (!(gem.Quality == Quality.Superior)) continue;
                    if (gem.Unique) continue;
                }
                if (bad) continue;
                retval.Add(item);
            }
            List<Item> removeme = new List<Item>();
            for(int i = 0;i<retval.Count;i++)
            {
                Item item = retval[i];
                for(int k = i+1;k<retval.Count;k++)
                {
                    Item cmp = retval[k];
                    if (item.Name == cmp.Name)
                    {
                        if (cmp.Level < item.Level && cmp.Level < 100)
                        {
                            if(!removeme.Contains(cmp))
                            {
                                removeme.Add(cmp);
                            }
                        }
                    }
                }
            }
            foreach (Item item in removeme)
            {
                retval.Remove(item);
            }
            return retval;
        }
        public virtual int getAttributesStart()
        {
            return 0;
        }
        public virtual int getAttributesEnd()
        {
            return this.getAttributes().Length - 1;
        }
        public bool CheckValidity(Item[] items)
        {
            int finger = (int)SlotName.Finger1;
            int trinket = (int)SlotName.Trinket1;
            int weapon = (int)SlotName.MainHand;
            if (items[finger].Unique && items[finger + 1].Unique)
            {
                if (items[finger].Id == items[finger + 1].Id)
                {
                    return false;
                }
            }
            if (items[trinket].Unique && items[trinket + 1].Unique)
            {
                if (items[trinket].Id == items[trinket + 1].Id)
                {
                    return false;
                }
            }
            if (items[weapon].Unique && items[weapon + 1].Unique)
            {
                if (items[weapon].Id == items[weapon + 1].Id)
                {
                    return false;
                }
            }
            Weapon mh = (Weapon)items[(int)SlotName.MainHand];
            if (mh.TwoHand)
            {
                items[(int)SlotName.OffHand] = Weapon.None;
            }
            return true;
        }
        /// <summary>
        /// This function transforsm the list of items into array slot-indexed array
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public virtual List<Item>[] generateItemList(List<Item> items)
        {
            List<string> OHblackList = new List<string>();
            int slotcount = Character.SlotCount;
            List<Item>[] retval = new List<Item>[slotcount + 1];
            for (int i = 0; i < retval.Length; i++)
            {
                retval[i] = new List<Item>();
                // { None = -1, Head = 0, Neck, Shoulder, Back, Chest, Wrist, Hands, Waist, Legs, Feet, Finger1, Finger2, Trinket1, Trinket2, MainHand, OffHand, Ranged };
                retval[i].Add(Item.getPlaceholderItem(i));
            }
            foreach (Item item in items)
            {
                if (item.filterItem()) continue;
                if (item.GetType() == typeof(Armor))
                {
                    Armor armor = (Armor)item;
                    if ((int)armor.Slot == -1)
                    {
                        continue;
                    }
                    if ((int)armor.Slot < 10)
                    {
                        retval[(int)armor.Slot].Add(item);
                    }
                    else // shield/ohindex
                    {
                        if (armor.Slot == ArmorSlot.Shield && !m_AllowShield) continue;
                        retval[(int)SlotName.OffHand].Add(item);
                    }
                }
                else if (item.GetType() == typeof(Gem))
                {
                    retval[slotcount].Add(item);
                }
                else if (item.GetType() == typeof(Ring))
                {
                    retval[(int)SlotName.Finger1].Add((Ring)item.Clone());
                    retval[(int)SlotName.Finger2].Add((Ring)item.Clone());
                }
                else if (item.GetType() == typeof(Trinket))
                {
                    retval[(int)SlotName.Trinket1].Add((Trinket)item.Clone());
                    retval[(int)SlotName.Trinket2].Add((Trinket)item.Clone());
                }
                else if (item.GetType() == typeof(Weapon))
                {
                    Weapon weapon = (Weapon)item;
                    if (weapon.TwoHand && !m_AllowTwoHand) continue;
                    if ((weapon.WeaponSlot & WeaponSlot.MainHand) > 0)
                    {
                        retval[(int)SlotName.MainHand].Add((Weapon) item.Clone());
                    }
                    if((weapon.WeaponSlot & WeaponSlot.OffHand) > 0)
                    {
                        bool found = false;
                        foreach (Attribute attr in weapon.Attributes)
                        {
                            if (attr.Type == AttributeName.SpellDamage || attr.Type == AttributeName.Healing) found = true;
                        }
                        if (found)
                        {
                            continue;
                        }
                        else
                        {
                            if (m_AllowDualWield) retval[(int)SlotName.OffHand].Add((Weapon)item.Clone());
                        }
                    }
                    if (weapon.WeaponSlot == WeaponSlot.Ranged)
                    {
                        retval[(int)SlotName.Ranged].Add(item);
                    }
                }
            }
            return retval;
        }
        /// <summary>
        /// weapon skill handling
        /// </summary>
        /// <returns></returns>
        public float[] evaluteWeaponSkills()
        {
            float[] retval = new float[3];
            List<WeaponSkill> skills = new List<WeaponSkill>();
            skills.AddRange(m_BaseWeaponSkills);
            skills.AddRange(m_WeaponSkills);
            foreach (WeaponSkill skill in skills)
            {
                if (skill.Weapon == ((Weapon)m_Items[(int)SlotName.MainHand]).Type)
                {
                    retval[0] += skill.Value / WeaponSkillRating;
                }
                if (skill.Weapon == ((Weapon)m_Items[(int)SlotName.OffHand]).Type)
                {
                    retval[1] += skill.Value / WeaponSkillRating;
                }
                if (skill.Weapon == ((Weapon)m_Items[(int)SlotName.Ranged]).Type)
                {
                    retval[2] += skill.Value / WeaponSkillRating;
                }
            }
            return retval;
        }
        /// <summary>
        /// hack for weapon skill clean up
        /// </summary>
        public void CleanUp()
        {
            m_WeaponSkills = new List<WeaponSkill>();
        }
        /// <summary>
        /// base evaluate item function, evaluating all the attributes of an item
        /// </summary>
        /// <param name="item"></param>
        /// <param name="slot"></param>
        /// <param name="m_Equip"></param>
        public virtual void evaluateItem(Item item, int slot)
        {
            List<Attribute> attrs = new List<Attribute>();
            attrs.AddRange(item.Attributes);
            attrs.AddRange(item.Enchant.Attributes);
            foreach (Attribute attr in attrs)
            {
                if (attr.Type == AttributeName.Special)
                {
                    handleAttribute(Item.getSpecialAttribute((int)attr.Value, this));
                }
                else handleAttribute(attr);
            }
        }
        /// <summary>
        /// evaluat buffs
        /// </summary>
        /// <param name="buffs"></param>
        /// <returns></returns>
        public void evaluateBuffs(List<Buff> buffs)
        {
            m_Equip.Mode = 1;
            m_Equip.ResetBuffs();
            foreach (Buff buff in buffs)
            {
                foreach (Attribute attr in buff.Attributes)
                {
                    if (attr.Type == AttributeName.Special)
                    {
                        handleAttribute(Item.getSpecialAttribute((int)attr.Value, this));
                    }
                    else handleAttribute(attr);
                }
            }
            m_PermSpecialBuffs = this.SpecialValues.Count;
        }
        public void evaluateBuffs()
        {
            m_Equip.Mode = 1;
            m_Equip.ResetBuffs();
            evaluateBuffs(m_Buffs);
        }
        /// <summary>
        /// main evaluate Equip function, adding up buffs and handling sockets and sets
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public virtual bool evaluateEquip(Item[] items)
        {
            if (!CheckValidity(items)) return false;
            m_Equip.Mode = 2;
            m_Equip.ResetEquip();
            if (this.SpecialValues.Count > 0)
            {
                this.SpecialValues.RemoveRange(m_PermSpecialBuffs, this.SpecialValues.Count - m_PermSpecialBuffs);
            }
            List<string> itemNames = new List<string>();
            for (int i = 0; i < items.Length; i++)
            {
                Item item = items[i];
                itemNames.Add(item.Name);
                evaluateItem(item, i);
            }
            // external set handling
            List<string> setNames = new List<string>();
            for (int i = 0; i < items.Length; i++)
            {
                Item item = items[i];
                if (item.Set.Name != null && !setNames.Contains(item.Set.Name))
                {
                    int counter = 0;
                    int lastfound = 0;
                    for (int k = 0; k < item.Set.Item.Count; k++)
                    {
                        for (int m = lastfound; m < items.Length; m++)
                        {
                            if (item.Set.Item[k] == items[m].Name)
                            {
                                counter++;
                                lastfound = m+1;
                                break;
                            }
                        }
                    }
                    for (int k = 0; k < item.Set.Bonus.Count; k++)
                    {
                        if (counter >= item.Set.Treshold[k])
                        {
                            Attribute attr = item.Set.Bonus[k];
                            if (attr.Type == AttributeName.Special)
                            {
                                handleAttribute(Item.getSpecialAttribute((int)attr.Value, this));
                            }
                            else handleAttribute(attr);
                        }
                    }
                    setNames.Add(item.Set.Name);
                }
            }
            // external socket handling
            for (int i = 0; i < items.Length; i++)
            {
                Item item = items[i];
                bool match = true;
                if (item.SocketCount > 0)
                {
                    for (int k = 0; k < item.SocketCount; k++)
                    {
                        if (item.Gems[k] != null)
                        {
                            for (int l = 0; l < item.Gems[k].Attributes.Count; l++)
                            {
                                Attribute attr = item.Gems[k].Attributes[l];
                                if (attr.Type == AttributeName.Special)
                                {
                                    handleAttribute(Item.getSpecialAttribute((int)attr.Value, this));
                                }
                                else handleAttribute(attr);
                            }
                            if ((((GemType)item.Gems[k].Type) & item.GemColors[k]) == 0) match = false;
                        }
                    }
                    if (match)
                    {
                        foreach (Attribute attr in item.GemBonus)
                        {
                            if (attr.Type == AttributeName.Special)
                            {
                                handleAttribute(Item.getSpecialAttribute((int)attr.Value, this));
                            }
                            else handleAttribute(attr);
                        }
                    }
                }
            }
            m_Equip.Mode = 3;
            m_CondFactor = 1.0f;
            foreach (AttributeName attrname in m_Conditions.Keys)
            {
                if (m_Equip[attrname] < m_Conditions[attrname])
                {
                    m_CondFactor *= (float)Math.Pow(Math.Min(m_Equip[attrname] / m_Conditions[attrname], 1.0f), 2.0f);
                }
            }
            m_Items = items;
            return true;
        }
        /// <summary>
        /// slot count 
        /// </summary>
        public static int SlotCount = ((int[])Enum.GetValues(typeof(SlotName))).Length - 1;

        #region ICloneable Member

        public object Clone()
        {
            return this.MemberwiseClone();
        }

        #endregion
    }
}
